#include <zenoui/draws/BezierCurveDraw.hpp>
#include <zenoui/check_gl.hpp>

namespace zenoui {

ZENO_HOT_FN static float sdfLineSegment(const glm::vec2& p, const glm::vec2& p0, const glm::vec2& p1) {
    glm::vec2 v = p1 - p0;
    glm::vec2 w = p - p0;

    float c1 = glm::dot(w, v);
    if (c1 <= 0)
        return glm::distance(p, p0);

    float c2 = glm::dot(v, v);
    if (c2 <= c1)
        return glm::distance(p, p1);

    float b = c1 / c2;
    glm::vec2 pb = p0 + b * v;

    return glm::distance(p, pb);
}

ZENO_HOT_FN glm::vec2 BezierCurveDraw::position_at_parameter(float t) const {
    float s = 1 - t, s2 = s * s, s3 = s2 * s, t2 = t * t, t3 = t2 * t;
    return s3 * p1 + 3 * s2 * t * cp1 + 3 * s * t2 * cp2 + t3 * p2;
}

ZENO_HOT_FN BezierCurveDrawBaked BezierCurveDraw::bake() const {
    BezierCurveDrawBaked baked;
    baked.width = width;
    baked.points.resize(numSegs + 1);
    glm::vec2 minpos(std::numeric_limits<float>::max());
    glm::vec2 maxpos(std::numeric_limits<float>::min());
    for (size_t i = 0; i <= numSegs; i++) {
        float t = static_cast<float>(i) / numSegs;
        auto pos = position_at_parameter(t);
        minpos = glm::min(minpos, pos);
        maxpos = glm::max(maxpos, pos);
        baked.points[i] = pos;
    }
    baked.minpos = minpos;
    baked.maxpos = maxpos;
    return baked;
}

ZENO_HOT_FN void BezierCurveDrawBaked::paint(glm::vec4 const &color) {
    CHECK_GL(glLineWidth(width));
    CHECK_GL(glColor4fv(glm::value_ptr(color)));
    glBegin(GL_LINE_STRIP);
    for (size_t i = 0; i != points.size(); i++) {
        glVertex2fv(glm::value_ptr(points[i]));
    }
    CHECK_GL(glEnd());
}

ZENO_HOT_FN bool BezierCurveDrawBaked::contains(glm::vec2 const &pos) const { // todo: precalc bbox?
    if (glm::any(glm::lessThan(pos, minpos)) || glm::any(glm::greaterThan(pos, maxpos))) return false;
    glm::vec2 last_pos = points[0];
    for (size_t i = 1; i != points.size(); i++) {
        auto curr_pos = points[i];
        if (sdfLineSegment(pos, last_pos, curr_pos) <= width + 1e-5f) [[unlikely]] {
            return true;
        }
        last_pos = curr_pos;
    }
    return false;
}

}
